<!DOCTYPE html>
<html lang="en" class="h-full">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Pyxel Code Maker</title>
    <script src="https://cdn.tailwindcss.com/3.4.17"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ace.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ext-language_tools.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/kitao/pyxel/wasm/pyxel.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
    <style type="text/tailwindcss">
      @layer components {
        .page {
          @apply bg-gray-900 text-gray-100 antialiased;
        }
        .toolbar {
          @apply flex items-center gap-4 bg-gray-900 px-4 py-3;
        }
        .btn {
          @apply bg-gray-900 px-2 py-1 border border-gray-600 rounded-md w-14 font-medium text-gray-300 text-xs text-center;
        }
        .btn:disabled {
          @apply bg-gray-700 opacity-50 text-gray-400 pointer-events-none;
        }
        .btn.active {
          @apply bg-slate-600 text-white;
        }
        .tool-title {
          @apply mr-2 font-semibold text-gray-200 text-2xl tracking-tight select-none;
        }
        .tool-tagline {
          @apply text-gray-300 text-sm leading-5 whitespace-nowrap select-none;
        }
        .link {
          @apply text-indigo-400 hover:text-indigo-300 visited:text-indigo-400 underline underline-offset-2;
        }
        .seg {
          @apply flex items-center bg-gray-900 border border-gray-600 rounded-md divide-x divide-gray-700 overflow-hidden;
        }
        .seg-btn {
          @apply flex-1 bg-gray-900 px-3 py-1 border-none min-w-[2rem] font-medium text-gray-300 text-xs text-center;
        }
        .seg-btn.disabled {
          @apply bg-gray-700 opacity-50 text-gray-400 pointer-events-none;
        }
        .seg-active {
          @apply bg-slate-600 text-white;
        }
        .focus-line {
          @apply flex w-full h-[1px];
        }
        .focus-line-segment {
          @apply flex-1 bg-gray-700;
        }
        .focus-line-segment-active {
          @apply bg-cyan-700;
        }
      }

      #left-pane.res-mode {
        background-color: var(--pyxel-background-color);
      }
    </style>
  </head>

  <body class="h-full page">
    <div id="app-root" class="flex flex-col h-full min-h-0">
      <!-- Toolbar -->
      <div class="toolbar">
        <div class="flex items-baseline gap-2 min-w-0">
          <div class="tool-title">Pyxel Code Maker</div>
          <div class="truncate tool-tagline">
            Playground for
            <a class="link" href="https://github.com/kitao/pyxel" tabindex="-1"
              >Pyxel</a
            >, a retro game engine for Python.
          </div>
        </div>
        <!-- Controls -->
        <div class="flex flex-1 justify-start items-center mt-0.5 min-w-0">
          <!-- Load / Save -->
          <div class="flex items-center gap-2">
            <button
              id="load-button"
              class="btn"
              tabindex="-1"
              title="Ctrl/Cmd+L: Load"
            >
              Load
            </button>
            <button
              id="save-button"
              class="btn"
              tabindex="-1"
              title="Ctrl/Cmd+S: Save"
            >
              Save
            </button>
          </div>
          <!-- Code / Res toggle -->
          <div class="flex items-center px-4">
            <div class="seg" role="group">
              <button
                id="code-tab-button"
                class="seg-btn seg-active"
                type="button"
                aria-pressed="true"
                title="Ctrl/Cmd+,: Toggle Code/Res"
                tabindex="-1"
              >
                Code
              </button>
              <button
                id="resource-tab-button"
                class="seg-btn disabled"
                type="button"
                aria-pressed="false"
                title="Ctrl/Cmd+,: Toggle Code/Res"
                tabindex="-1"
              >
                Res
              </button>
            </div>
          </div>
          <!-- Run -->
          <div class="flex items-center gap-2">
            <button
              id="run-button"
              class="btn"
              disabled
              title="Ctrl/Cmd+Enter: Run"
              tabindex="-1"
            >
              Run
            </button>
          </div>
        </div>
      </div>
      <div id="focus-line" class="focus-line">
        <div id="focus-line-left" class="focus-line-segment"></div>
        <div id="focus-line-center" class="focus-line-segment"></div>
        <div id="focus-line-right" class="focus-line-segment"></div>
      </div>

      <!-- Main content -->
      <div id="main-splitter" class="flex flex-1 min-h-0">
        <!-- Left pane -->
        <div
          id="left-pane"
          class="flex flex-col min-h-0"
          style="width: 50%; min-width: 200px; max-width: 80%"
        >
          <!-- Code editor -->
          <div id="code-pane" class="flex flex-1 min-h-0">
            <div id="code-editor" class="w-full h-full"></div>
          </div>

          <!-- Resource editor -->
          <div id="resource-pane" class="hidden flex flex-1 min-h-0">
            <iframe
              id="resource-editor"
              class="border-0 w-full h-full"
              style="background-color: var(--pyxel-background-color)"
              title="Pyxel Resource Editor"
            ></iframe>
          </div>
        </div>

        <!-- Splitter -->
        <div
          id="splitter-handle"
          class="z-50 relative w-3 cursor-col-resize select-none"
          style="
            background-color: #000000;
            touch-action: none;
            pointer-events: none;
          "
          onpointerenter="this.style.backgroundColor='#3a4a5f'"
          onpointerleave="this.style.backgroundColor='#232d3b'"
          title="Ctrl/Cmd+.: Toggle Left/Right Focus"
        ></div>

        <!-- Right pane -->
        <div
          class="flex flex-col flex-1 min-h-0"
          style="background-color: var(--pyxel-background-color)"
        >
          <iframe
            id="runtime-screen"
            class="flex-1 border-0 w-full h-full min-h-0"
            style="background-color: var(--pyxel-background-color)"
            title="Pyxel Runtime"
          ></iframe>
        </div>
      </div>
    </div>

    <input
      id="load-file"
      type="file"
      accept=".zip,application/zip"
      class="hidden"
      tabindex="-1"
    />

    <script>
      const runtimeScreen = document.getElementById("runtime-screen");
      const resourceEditor = document.getElementById("resource-editor");

      window.project = {
        name: "pyxel-project",
        code: "",
        resource: "",
        files: {},
      };
      window.isCopyingResource = false;

      function uint8ToBase64(u8) {
        let bin = "";
        for (let i = 0; i < u8.length; i++) {
          bin += String.fromCharCode(u8[i]);
        }
        return btoa(bin);
      }

      function base64ToUint8(b64) {
        const bin = atob(b64 || "");
        const bytes = new Uint8Array(bin.length);
        for (let i = 0; i < bin.length; i++) {
          bytes[i] = bin.charCodeAt(i);
        }
        return bytes;
      }

      function copyCodeToProject() {
        window.project.code = window.codeEditor.getValue();
      }

      async function copyResourceToProject() {
        if (!resourceEditor.contentWindow?.pyxelContext?.pyodide) {
          return;
        }

        window.isCopyingResource = true;

        try {
          const doc = resourceEditor.contentWindow.document;
          doc.dispatchEvent(
            new KeyboardEvent("keydown", {
              key: "F13",
              code: "F13",
              bubbles: true,
            })
          );
          doc.dispatchEvent(
            new KeyboardEvent("keyup", {
              key: "F13",
              code: "F13",
              bubbles: true,
            })
          );

          await new Promise((resolve) => setTimeout(resolve, 100));

          window.project.resource = uint8ToBase64(
            resourceEditor.contentWindow.pyxelContext.pyodide.FS.readFile(
              PYXEL_WORKING_DIRECTORY + "/my_resource.pyxres"
            )
          );
        } finally {
          window.isCopyingResource = false;
        }
      }

      function resetRuntimeScreen() {
        runtimeScreen.contentWindow?.resetPyxel();
      }

      function resetResourceEditor() {
        resourceEditor.contentWindow?.resetPyxel();
      }

      async function loadProject(file) {
        try {
          const buf = await file.arrayBuffer();
          const zip = await JSZip.loadAsync(buf);
          const files = {};

          for (const [path, entry] of Object.entries(zip.files)) {
            if (!entry.dir) {
              const relativePath = path.split("/").slice(1).join("/");
              const content = await entry.async("uint8array");
              files[relativePath] = uint8ToBase64(content);
            }
          }

          if (!files["main.py"]) {
            throw new Error("Missing main.py");
          }
          if (!files["my_resource.pyxres"]) {
            throw new Error("Missing my_resource.pyxres");
          }

          window.project.name = file.name.replace(/\.zip$/i, "");
          window.project.files = files;
          window.project.code = new TextDecoder().decode(
            base64ToUint8(files["main.py"])
          );
          window.project.resource = files["my_resource.pyxres"];

          window.codeEditor.setValue(window.project.code, -1);
          resetRuntimeScreen();
          resetResourceEditor();
        } catch (e) {
          alert("Load failed: " + e.message);
        }
      }

      async function saveProject() {
        if (window.isCopyingResource) {
          return;
        }

        copyCodeToProject();
        await copyResourceToProject();
        resetRuntimeScreen();

        if (window.showSaveFilePicker) {
          try {
            const handle = await window.showSaveFilePicker({
              suggestedName: window.project.name + ".zip",
              types: [
                {
                  description: "ZIP",
                  accept: { "application/zip": [".zip"] },
                },
              ],
            });

            window.project.name = handle.name.replace(/\.zip$/i, "");

            const archive = new JSZip();

            for (const [relativePath, base64Content] of Object.entries(
              window.project.files
            )) {
              archive.file(
                `${window.project.name}/${relativePath}`,
                base64ToUint8(base64Content)
              );
            }

            archive.file(`${window.project.name}/main.py`, window.project.code);
            archive.file(
              `${window.project.name}/my_resource.pyxres`,
              base64ToUint8(window.project.resource)
            );

            const blob = await archive.generateAsync({ type: "blob" });
            const writable = await handle.createWritable();
            await writable.write(blob);
            await writable.close();
            return;
          } catch (e) {
            if (e.name === "AbortError") {
              return;
            }
            alert("Save failed: " + e.message);
          }
        }

        // Fallback: traditional download link method
        const archive = new JSZip();

        for (const [relativePath, base64Content] of Object.entries(
          window.project.files
        )) {
          archive.file(
            `${window.project.name}/${relativePath}`,
            base64ToUint8(base64Content)
          );
        }

        archive.file(`${window.project.name}/main.py`, window.project.code);
        archive.file(
          `${window.project.name}/my_resource.pyxres`,
          base64ToUint8(window.project.resource)
        );

        const blob = await archive.generateAsync({ type: "blob" });

        const a = document.createElement("a");
        a.href = URL.createObjectURL(blob);
        a.download = window.project.name + ".zip";
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(a.href);
      }

      function initAceEditor() {
        const codeEditor = ace.edit("code-editor");

        codeEditor.setTheme("ace/theme/monokai");
        codeEditor.session.setMode("ace/mode/python");
        codeEditor.renderer.setScrollMargin(8, 0, 0, 0);
        codeEditor.renderer.setPadding(8);

        codeEditor.setOptions({
          fontSize: "14px",
          showPrintMargin: false,
          highlightActiveLine: true,
          enableBasicAutocompletion: true,
          enableLiveAutocompletion: true,
        });

        codeEditor.container.style.backgroundColor = "#111827";

        const gutter = codeEditor.renderer.$gutter;
        if (gutter?.style) {
          gutter.style.background = "#232d3b";
        }

        const aceThemeStyle = document.createElement("style");
        aceThemeStyle.textContent = `
          .ace-monokai .ace_marker-layer .ace_active-line,
          .ace_monokai .ace_marker-layer .ace_active-line {
            background-color: #232d3b !important;
          }

          .ace-monokai .ace_gutter-active-line,
          .ace_monokai .ace_gutter-active-line {
            background-color: #374151 !important;
          }
        `;
        document.head.appendChild(aceThemeStyle);

        window.codeEditor = codeEditor;
      }

      function animateButton(button) {
        button.classList.add("active");
        setTimeout(() => button.classList.remove("active"), 150);
      }

      function setupButtonHandlers() {
        const loadButton = document.getElementById("load-button");
        const loadFileInput = document.getElementById("load-file");
        const saveButton = document.getElementById("save-button");
        const codeTabButton = document.getElementById("code-tab-button");
        const resourceTabButton = document.getElementById(
          "resource-tab-button"
        );
        const codePane = document.getElementById("code-pane");
        const resPane = document.getElementById("resource-pane");
        const leftPane = document.getElementById("left-pane");
        const runButton = document.getElementById("run-button");

        loadButton.addEventListener("click", () => {
          animateButton(loadButton);
          loadFileInput.value = "";
          loadFileInput.click();
        });

        loadFileInput.addEventListener("change", async (e) => {
          const file = e.target.files?.[0];
          if (file) {
            loadProject(file);
          }
        });

        saveButton.addEventListener("click", async () => {
          animateButton(saveButton);
          await saveProject();
        });

        codeTabButton.addEventListener("click", () => {
          codePane.classList.remove("hidden");
          resPane.classList.add("hidden");
          codeTabButton.classList.add("seg-active");
          resourceTabButton.classList.remove("seg-active");
          codeTabButton.setAttribute("aria-pressed", "true");
          resourceTabButton.setAttribute("aria-pressed", "false");
          leftPane.classList.remove("res-mode");
          setTimeout(() => {
            window.codeEditor.resize();
            window.codeEditor.focus();
            updateFocusLine("left");
          }, 0);
        });

        resourceTabButton.addEventListener("click", () => {
          codePane.classList.add("hidden");
          resPane.classList.remove("hidden");
          codeTabButton.classList.remove("seg-active");
          resourceTabButton.classList.add("seg-active");
          codeTabButton.setAttribute("aria-pressed", "false");
          resourceTabButton.setAttribute("aria-pressed", "true");
          leftPane.classList.add("res-mode");
          setTimeout(() => {
            if (resourceEditor.contentWindow) {
              resourceEditor.contentWindow.focus();
            }
            updateFocusLine("left");
          }, 0);
        });

        runButton.addEventListener("click", async () => {
          animateButton(runButton);
          copyCodeToProject();
          await copyResourceToProject();
          resetRuntimeScreen();
        });
      }

      async function loadInitialFiles() {
        window.project.code = await (await fetch("main.py")).text();
        window.project.resource = uint8ToBase64(
          new Uint8Array(
            await (await fetch("my_resource.pyxres")).arrayBuffer()
          )
        );
        window.project.files = {
          ".pyxapp_startup_script": uint8ToBase64(
            new TextEncoder().encode("main.py")
          ),
          "main.py": uint8ToBase64(
            new TextEncoder().encode(window.project.code)
          ),
          "my_resource.pyxres": window.project.resource,
        };

        window.codeEditor.setValue(window.project.code, -1);
        window.codeEditor.focus();
      }

      async function setupIframes() {
        const screenLoaded = new Promise((resolve) => {
          runtimeScreen.onload = () => resolve();
        });

        const editorLoaded = new Promise((resolve) => {
          resourceEditor.onload = () => resolve();
        });

        runtimeScreen.src = "pyxel-screen.html";
        resourceEditor.src = "pyxel-editor.html";

        resourceEditor.addEventListener("mouseenter", () => {
          if (resourceEditor.contentWindow) {
            resourceEditor.contentWindow.focus();
          }
        });

        await Promise.all([screenLoaded, editorLoaded]);
      }

      function waitForPyxelReady() {
        if (
          runtimeScreen?.contentWindow?.pyxelContext?.resolveInput &&
          resourceEditor?.contentWindow?.pyxelContext?.resolveInput
        ) {
          resourceEditor.contentWindow._savePyxelFile = (filename) => {
            saveProject();
          };

          const runButton = document.getElementById("run-button");
          runButton.disabled = false;

          const resourceTabButton = document.getElementById(
            "resource-tab-button"
          );
          resourceTabButton.disabled = false;
          resourceTabButton.classList.remove("disabled");

          const splitterHandleEl = document.getElementById("splitter-handle");
          splitterHandleEl.style.pointerEvents = "auto";
          splitterHandleEl.style.backgroundColor = "#232d3b";
        } else {
          setTimeout(waitForPyxelReady, 100);
        }
      }

      function updateFocusLine(state) {
        const left = document.getElementById("focus-line-left");
        const right = document.getElementById("focus-line-right");
        if (!left || !right) {
          return;
        }
        left.classList.remove("focus-line-segment-active");
        right.classList.remove("focus-line-segment-active");

        if (state === "left") {
          left.classList.add("focus-line-segment-active");
        } else if (state === "right") {
          right.classList.add("focus-line-segment-active");
        }
      }

      function setupFocusIndicator() {
        const leftPane = document.getElementById("left-pane");
        const rightPane =
          document.getElementById("main-splitter").lastElementChild;
        const toolbar = document.querySelector(".toolbar");
        const splitterHandle = document.getElementById("splitter-handle");

        document.addEventListener(
          "pointerdown",
          (e) => {
            const target = e.target;

            if (toolbar && toolbar.contains(target)) {
              return;
            }

            if (splitterHandle && splitterHandle.contains(target)) {
              return;
            }

            if (leftPane.contains(target)) {
              updateFocusLine("left");
            } else if (rightPane.contains(target)) {
              updateFocusLine("right");
            } else {
              updateFocusLine("none");
            }
          },
          true
        );

        if (runtimeScreen?.contentWindow?.document) {
          runtimeScreen.contentWindow.document.addEventListener(
            "pointerdown",
            () => {
              updateFocusLine("right");
            },
            true
          );
        }

        if (resourceEditor?.contentWindow?.document) {
          resourceEditor.contentWindow.document.addEventListener(
            "pointerdown",
            () => {
              updateFocusLine("left");
            },
            true
          );
        }
      }

      function setupSplitter() {
        const mainSplit = document.getElementById("main-splitter");
        const leftPaneEl = document.getElementById("left-pane");
        const dividerHitEl = document.getElementById("splitter-handle");

        const focusLeft = document.getElementById("focus-line-left");
        const focusCenter = document.getElementById("focus-line-center");
        const focusRight = document.getElementById("focus-line-right");

        function updateFocusLineLayout() {
          if (!focusLeft || !focusCenter || !focusRight) {
            return;
          }

          const containerRect = mainSplit.getBoundingClientRect();
          const leftRect = leftPaneEl.getBoundingClientRect();
          const handleRect = dividerHitEl.getBoundingClientRect();

          const containerWidth = containerRect.width;
          const leftWidth = leftRect.width;
          const centerWidth = handleRect.width;
          const rightWidth = Math.max(
            containerWidth - leftWidth - centerWidth,
            0
          );

          // 左・中央・右をピクセル指定にして、セパレーター分を中央に集約
          focusLeft.style.flex = "0 0 auto";
          focusCenter.style.flex = "0 0 auto";
          focusRight.style.flex = "0 0 auto";

          focusLeft.style.width = leftWidth + "px";
          focusCenter.style.width = centerWidth + "px";
          focusRight.style.width = rightWidth + "px";
        }

        let dragging = false;
        let startX = 0;
        let startWidth = 0;

        function onMove(e) {
          if (!dragging) {
            return;
          }
          const dx = e.clientX - startX;
          let newW = startWidth + dx;
          const containerW = mainSplit.getBoundingClientRect().width;
          const minW = Math.min(200, containerW / 2);
          const maxW = containerW - minW;
          newW = Math.max(minW, Math.min(maxW, newW));
          leftPaneEl.style.width = newW + "px";
          window.codeEditor.resize();
          updateFocusLineLayout();
        }

        function onUp() {
          if (!dragging) {
            return;
          }
          dragging = false;
          window.removeEventListener("pointermove", onMove);
          window.removeEventListener("pointerup", onUp);
          window.removeEventListener("pointercancel", onUp);
          document.body.style.cursor = "";
          document.body.style.userSelect = "";
          runtimeScreen.style.display = "";
          resourceEditor.style.display = "";
        }

        function onDown(e) {
          dragging = true;
          startX = e.clientX;
          startWidth = leftPaneEl.getBoundingClientRect().width;
          document.body.style.cursor = "col-resize";
          document.body.style.userSelect = "none";
          runtimeScreen.style.display = "none";
          resourceEditor.style.display = "none";
          window.addEventListener("pointermove", onMove);
          window.addEventListener("pointerup", onUp);
          window.addEventListener("pointercancel", onUp);
          e.preventDefault();
        }

        dividerHitEl.addEventListener("pointerdown", onDown);
        updateFocusLineLayout();
        window.addEventListener("resize", updateFocusLineLayout);
      }

      function setupShortcuts() {
        function handleShortcutKeydown(e) {
          // Ctrl/Cmd+L: Load
          if ((e.ctrlKey || e.metaKey) && e.key === "l") {
            e.preventDefault();
            e.stopPropagation();
            const loadButton = document.getElementById("load-button");
            if (loadButton) {
              loadButton.click();
            }
            return;
          }

          // Ctrl/Cmd+S: Save
          if ((e.ctrlKey || e.metaKey) && e.key === "s") {
            e.preventDefault();
            e.stopPropagation();
            const saveButton = document.getElementById("save-button");
            if (saveButton) {
              saveButton.click();
            }
            return;
          }

          // Ctrl/Cmd+,: Toggle Code/Res
          if ((e.ctrlKey || e.metaKey) && e.key === ",") {
            e.preventDefault();
            e.stopPropagation();
            const codeTabButton = document.getElementById("code-tab-button");
            const resourceTabButton = document.getElementById(
              "resource-tab-button"
            );
            if (!codeTabButton || !resourceTabButton) {
              return;
            }
            const onCode = codeTabButton.classList.contains("seg-active");
            (onCode ? resourceTabButton : codeTabButton).click();
            if (!onCode) {
              setTimeout(() => window.codeEditor.focus(), 0);
            }
            return;
          }

          // Ctrl/Cmd+.: Toggle focus between left and right panes
          if ((e.ctrlKey || e.metaKey) && e.key === ".") {
            e.preventDefault();
            e.stopPropagation();

            const leftLine = document.getElementById("focus-line-left");
            const rightLine = document.getElementById("focus-line-right");
            const codeTabButton = document.getElementById("code-tab-button");
            const resourceTabButton = document.getElementById(
              "resource-tab-button"
            );

            const leftActive = leftLine?.classList.contains(
              "focus-line-segment-active"
            );
            const rightActive = rightLine?.classList.contains(
              "focus-line-segment-active"
            );

            // Decide next target: if right is active, move to left, otherwise to right
            const moveToLeft = rightActive || (!leftActive && !rightActive);

            if (moveToLeft) {
              if (codeTabButton?.classList.contains("seg-active")) {
                if (window.codeEditor) {
                  window.codeEditor.focus();
                }
              } else if (resourceTabButton?.classList.contains("seg-active")) {
                if (resourceEditor.contentWindow) {
                  resourceEditor.contentWindow.focus();
                }
              }
              updateFocusLine("left");
            } else {
              if (runtimeScreen.contentWindow) {
                runtimeScreen.contentWindow.focus();
              }
              updateFocusLine("right");
            }

            return;
          }

          // Ctrl/Cmd+Enter: Run
          if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
            e.preventDefault();
            e.stopPropagation();
            const runButton = document.getElementById("run-button");
            if (!runButton?.disabled) {
              runButton.click();
            }
            return;
          }
        }

        function bindTarget(tgt) {
          tgt.addEventListener("keydown", handleShortcutKeydown, {
            capture: true,
          });
          if (tgt.document) {
            tgt.document.addEventListener("keydown", handleShortcutKeydown, {
              capture: true,
            });
          }
        }

        bindTarget(window);
        bindTarget(runtimeScreen.contentWindow);
        bindTarget(resourceEditor.contentWindow);
      }

      function setupPyxelInput() {
        const handlers = ["click", "touchstart", "keydown"];
        function resolvePyxelInput() {
          if (runtimeScreen?.contentWindow?.pyxelContext?.resolveInput) {
            runtimeScreen.contentWindow.pyxelContext.resolveInput();
          }

          if (resourceEditor?.contentWindow?.pyxelContext?.resolveInput) {
            resourceEditor.contentWindow.pyxelContext.resolveInput();
          }
        }

        handlers.forEach((eventName) => {
          window.addEventListener(eventName, resolvePyxelInput);
        });
      }

      window.addEventListener("DOMContentLoaded", async () => {
        // UI basics
        initAceEditor();
        setupButtonHandlers();
        setupSplitter();

        // Load project
        await loadInitialFiles();
        updateFocusLine("left");

        // Prepare iframes and interactions
        await setupIframes();
        setupFocusIndicator();
        setupShortcuts();
        setupPyxelInput();

        // Enable Pyxel controls
        waitForPyxelReady();
      });
    </script>
  </body>
</html>
